NestJS REPL模式调试与数据插入实战
NestJS REPL模式调试与数据插入实战
学习目标:掌握 NestJS REPL 模式的使用、通过 REPL 模式调用 Service 方法、使用 include 查询关联数据。
一、NestJS REPL 模式概述
1.1 什么是 REPL 模式
code
REPL 模式介绍:
│
├── REPL 定义
│ ├── Read-Eval-Print-Loop
│ ├── 交互式编程环境
│ └── 实时执行代码并查看结果
│
├── NestJS REPL 模式特点
│ ├── 无需启动完整服务器
│ ├── 直接调用 Service 方法
│ ├── 实时调试业务逻辑
│ └── 快速验证功能
│
└── 使用场景
├── 跳过 Controller 直接测试 Service
├── 快速插入测试数据
├── 调试复杂查询逻辑
└── 验证关联关系1.2 REPL 模式优势
| 维度 | 传统方式 | REPL 模式 |
|---|---|---|
| 启动速度 | 需要启动完整服务器 | 快速启动 |
| 调试方式 | 需要通过 HTTP 请求 | 直接调用方法 |
| 数据准备 | 需要创建 Controller | 直接插入数据 |
| 交互性 | 需要使用 Postman | 命令行交互 |
| 效率 | 较慢 | 快速高效 |
二、启动 REPL 模式
2.1 启动命令
bash
# 启动 NestJS REPL 模式
pnpm start:repl
# 或使用 npx
npx nest start --repl
# 或使用 npm
npm run start:repl2.2 REPL 模式界面
bash
# 启动后的界面
$ pnpm start:repl
> my-project@1.0.0 start:repl
> nest start --repl
[Nest] LOG [NestFactory] Starting Nest application...
[Nest] LOG [InstanceLoader] AppModule dependencies initialized
[Nest] LOG [InstanceLoader] CourseModule dependencies initialized
[Nest] LOG [NestApplication] Nest application successfully started
REPL server is running.
Type `help` for more information.
>2.3 REPL 常用命令
code
REPL 常用命令:
│
├── help
│ └── 查看帮助信息
│
├── get <ServiceName>
│ └── 获取 Service 实例
│
├── await <expression>
│ └── 执行异步操作
│
├── .exit 或 Ctrl+D
│ └── 退出 REPL 模式
│
└── .clear
└── 清空上下文三、使用 REPL 模式插入数据
3.1 插入 Type 数据
步骤一:获取 CourseService 实例
javascript
// 在 REPL 中执行
await get(CourseService)步骤二:调用 createType 方法插入数据
javascript
// 插入第一条 type 数据
await get(CourseService).createType({
name: '推荐内容'
})
// 输出示例
{
id: 9,
name: '推荐内容',
createdAt: 2024-01-01T00:00:00.000Z,
updatedAt: 2024-01-01T00:00:00.000Z
}
// 插入更多 type 数据
await get(CourseService).createType({ name: '每日一个' })
await get(CourseService).createType({ name: '精品微课' })
await get(CourseService).createType({ name: '学习计划' })
await get(CourseService).createType({ name: '优质专栏' })3.2 插入 Tag 数据并关联 Type
关联关系说明
code
Tag 和 Type 的关联关系:
│
├── Type(类型)
│ ├── id: 9, name: '推荐内容'
│ ├── id: 10, name: '每日一个'
│ ├── id: 11, name: '精品微课'
│ ├── id: 12, name: '学习计划'
│ └── id: 13, name: '优质专栏'
│
└── Tag(标签)
├── id: 1, name: 'Vue3 项目实战', typeId: 9
├── id: 2, name: 'React18 新特性', typeId: 9
├── id: 3, name: 'TypeScript 进阶', typeId: 9
└── id: 4, name: 'Node.js 实战', typeId: 10插入 Tag 数据
javascript
// 插入 tag 并关联 type(typeId = 9 表示推荐内容)
await get(CourseService).createTag({
name: 'Vue3 项目实战',
typeId: 9
})
// 输出示例
{
id: 1,
name: 'Vue3 项目实战',
typeId: 9,
createdAt: 2024-01-01T00:00:00.000Z,
updatedAt: 2024-01-01T00:00:00.000Z
}
// 插入更多 tag 数据
await get(CourseService).createTag({ name: 'React18 新特性', typeId: 9 })
await get(CourseService).createTag({ name: 'TypeScript 进阶', typeId: 9 })
await get(CourseService).createTag({ name: 'Node.js 实战', typeId: 10 })
await get(CourseService).createTag({ name: 'Prisma 详解', typeId: 10 })3.3 完整的数据插入流程
code
数据插入完整流程:
│
├── 第一步:启动 REPL 模式
│ └── pnpm start:repl
│
├── 第二步:获取 Service 实例
│ └── await get(CourseService)
│
├── 第三步:插入 Type 数据
│ ├── createType({ name: '推荐内容' })
│ ├── createType({ name: '每日一个' })
│ ├── createType({ name: '精品微课' })
│ ├── createType({ name: '学习计划' })
│ └── createType({ name: '优质专栏' })
│
├── 第四步:插入 Tag 数据并关联
│ ├── createTag({ name: 'Vue3 项目实战', typeId: 9 })
│ ├── createTag({ name: 'React18 新特性', typeId: 9 })
│ ├── createTag({ name: 'TypeScript 进阶', typeId: 9 })
│ └── createTag({ name: 'Node.js 实战', typeId: 10 })
│
└── 第五步:验证数据
├── 查询数据库
└── 使用 Prisma Studio 查看四、使用 REPL 模式查询数据
4.1 查询 Tag 数据(包含关联的 Type)
javascript
// 查询所有 tag,并包含关联的 type
await get(CourseService).getTags()
// 输出示例
[
{
id: 1,
name: 'Vue3 项目实战',
typeId: 9,
type: {
id: 9,
name: '推荐内容'
}
},
{
id: 2,
name: 'React18 新特性',
typeId: 9,
type: {
id: 9,
name: '推荐内容'
}
},
{
id: 3,
name: 'TypeScript 进阶',
typeId: 9,
type: {
id: 9,
name: '推荐内容'
}
}
]4.2 查询 Type 数据(包含关联的 Tags)
Service 方法实现
typescript
// src/modules/course/course.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
@Injectable()
export class CourseService {
constructor(private prisma: PrismaService) {}
// 根据 ID 查询 type,并包含关联的 tags
async getTypeById(id: number) {
return this.prisma.courseTypes.findUnique({
where: { id },
include: {
tags: true, // 包含关联的 tags
},
});
}
// 查询所有 tags,并包含关联的 type
async getTags() {
return this.prisma.courseTags.findMany({
include: {
type: true, // 包含关联的 type
},
});
}
}REPL 模式查询
javascript
// 查询 ID 为 9 的 type,并包含关联的 tags
await get(CourseService).getTypeById(9)
// 输出示例
{
id: 9,
name: '推荐内容',
createdAt: 2024-01-01T00:00:00.000Z,
updatedAt: 2024-01-01T00:00:00.000Z,
tags: [
{
id: 1,
name: 'Vue3 项目实战',
typeId: 9
},
{
id: 2,
name: 'React18 新特性',
typeId: 9
},
{
id: 3,
name: 'TypeScript 进阶',
typeId: 9
}
]
}4.3 访问嵌套数据
javascript
// 查询 type 并获取第一条 tag
const result = await get(CourseService).getTypeById(9)
// 访问第一条 tag
result.tags[0]
// 输出
{
id: 1,
name: 'Vue3 项目实战',
typeId: 9
}
// 访问 tag 的 name
result.tags[0].name
// 输出:'Vue3 项目实战'五、Prisma include 详解
5.1 include 基本语法
typescript
// 基本语法
await prisma.model.findMany({
include: {
relationName: true, // 包含关联数据
},
});
// 查询 type 并包含 tags
await prisma.courseTypes.findUnique({
where: { id: 9 },
include: {
tags: true,
},
});5.2 include vs select
| 维度 | include | select |
|---|---|---|
| 作用 | 包含关联数据 | 选择特定字段 |
| 默认字段 | 包含所有字段 | 只包含选择的字段 |
| 关联数据 | 可以包含 | 可以包含 |
| 使用场景 | 需要完整数据和关联 | 只需要部分字段 |
typescript
// include:包含所有字段 + 关联数据
await prisma.courseTypes.findUnique({
where: { id: 9 },
include: {
tags: true,
},
});
// 返回:{ id, name, createdAt, updatedAt, tags }
// select:只选择特定字段 + 关联数据
await prisma.courseTypes.findUnique({
where: { id: 9 },
select: {
id: true,
name: true,
tags: {
select: {
id: true,
name: true,
},
},
},
});
// 返回:{ id, name, tags: [{ id, name }] }5.3 include 嵌套查询
typescript
// 多层嵌套 include
await prisma.courseTypes.findUnique({
where: { id: 9 },
include: {
tags: {
include: {
courses: true, // 包含 tag 关联的课程
},
},
},
});5.4 include 过滤和排序
typescript
// 查询 type 并包含过滤后的 tags
await prisma.courseTypes.findUnique({
where: { id: 9 },
include: {
tags: {
where: {
name: {
contains: 'Vue', // 只包含名称包含 'Vue' 的 tag
},
},
orderBy: {
createdAt: 'desc', // 按创建时间倒序
},
take: 5, // 最多返回 5 条
},
},
});六、REPL 模式实战场景
6.1 场景一:快速插入测试数据
javascript
// 批量插入 type 数据
const types = ['推荐内容', '每日一个', '精品微课', '学习计划', '优质专栏'];
for (const name of types) {
await get(CourseService).createType({ name });
console.log(`创建 type: ${name}`);
}
// 批量插入 tag 数据
const tags = [
{ name: 'Vue3 项目实战', typeId: 9 },
{ name: 'React18 新特性', typeId: 9 },
{ name: 'TypeScript 进阶', typeId: 9 },
{ name: 'Node.js 实战', typeId: 10 },
{ name: 'Prisma 详解', typeId: 10 },
];
for (const tag of tags) {
await get(CourseService).createTag(tag);
console.log(`创建 tag: ${tag.name}`);
}6.2 场景二:调试复杂查询
javascript
// 调试分页查询
const page = 1;
const size = 10;
const result = await get(CourseService).findAll(page, size);
console.log('总数据:', result.total);
console.log('当前页数据:', result.data.length);
// 调试关联查询
const typeWithTags = await get(CourseService).getTypeById(9);
console.log('Type 名称:', typeWithTags.name);
console.log('Tags 数量:', typeWithTags.tags.length);6.3 场景三:验证数据一致性
javascript
// 验证 type 和 tag 的关联关系
const type = await get(CourseService).getTypeById(9);
console.log('Type:', type.name);
console.log('关联的 Tags:');
type.tags.forEach((tag, index) => {
console.log(` ${index + 1}. ${tag.name} (ID: ${tag.id})`);
});
// 输出:
// Type: 推荐内容
// 关联的 Tags:
// 1. Vue3 项目实战 (ID: 1)
// 2. React18 新特性 (ID: 2)
// 3. TypeScript 进阶 (ID: 3)6.4 场景四:数据清理
javascript
// 清理测试数据
await get(CourseService).deleteAllTags()
await get(CourseService).deleteAllTypes()
console.log('测试数据已清理');七、完整实战示例
7.1 Service 层实现
typescript
// src/modules/course/course.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
import { CreateTypeDto } from './dto/create-type.dto';
import { CreateTagDto } from './dto/create-tag.dto';
@Injectable()
export class CourseService {
constructor(private prisma: PrismaService) {}
// 创建 type
async createType(createTypeDto: CreateTypeDto) {
return this.prisma.courseTypes.create({
data: createTypeDto,
});
}
// 创建 tag
async createTag(createTagDto: CreateTagDto) {
return this.prisma.courseTags.create({
data: createTagDto,
});
}
// 查询所有 tags(包含关联的 type)
async getTags() {
return this.prisma.courseTags.findMany({
include: {
type: true,
},
});
}
// 根据 ID 查询 type(包含关联的 tags)
async getTypeById(id: number) {
return this.prisma.courseTypes.findUnique({
where: { id },
include: {
tags: true,
},
});
}
// 查询所有 types(包含关联的 tags)
async getTypes() {
return this.prisma.courseTypes.findMany({
include: {
tags: true,
},
});
}
// 删除所有 tags
async deleteAllTags() {
return this.prisma.courseTags.deleteMany();
}
// 删除所有 types
async deleteAllTypes() {
return this.prisma.courseTypes.deleteMany();
}
}7.2 DTO 定义
typescript
// src/modules/course/dto/create-type.dto.ts
import { IsString, IsNotEmpty } from 'class-validator';
export class CreateTypeDto {
@IsString()
@IsNotEmpty()
name: string;
}typescript
// src/modules/course/dto/create-tag.dto.ts
import { IsString, IsNotEmpty, IsInt } from 'class-validator';
export class CreateTagDto {
@IsString()
@IsNotEmpty()
name: string;
@IsInt()
typeId: number;
}7.3 Prisma Schema
prisma
// prisma/schema.prisma
// 课程类型
model CourseTypes {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tags CourseTags[] // 一对多关系
@@map("course_types")
}
// 课程标签
model CourseTags {
id Int @id @default(autoincrement())
name String
typeId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
type CourseTypes @relation(fields: [typeId], references: [id])
@@map("course_tags")
}7.4 REPL 操作完整流程
javascript
// ========== 1. 启动 REPL ==========
// $ pnpm start:repl
// ========== 2. 插入 Type 数据 ==========
await get(CourseService).createType({ name: '推荐内容' })
// 返回:{ id: 9, name: '推荐内容', ... }
await get(CourseService).createType({ name: '每日一个' })
// 返回:{ id: 10, name: '每日一个', ... }
await get(CourseService).createType({ name: '精品微课' })
// 返回:{ id: 11, name: '精品微课', ... }
await get(CourseService).createType({ name: '学习计划' })
// 返回:{ id: 12, name: '学习计划', ... }
await get(CourseService).createType({ name: '优质专栏' })
// 返回:{ id: 13, name: '优质专栏', ... }
// ========== 3. 插入 Tag 数据并关联 Type ==========
await get(CourseService).createTag({ name: 'Vue3 项目实战', typeId: 9 })
// 返回:{ id: 1, name: 'Vue3 项目实战', typeId: 9, ... }
await get(CourseService).createTag({ name: 'React18 新特性', typeId: 9 })
// 返回:{ id: 2, name: 'React18 新特性', typeId: 9, ... }
await get(CourseService).createTag({ name: 'TypeScript 进阶', typeId: 9 })
// 返回:{ id: 3, name: 'TypeScript 进阶', typeId: 9, ... }
await get(CourseService).createTag({ name: 'Node.js 实战', typeId: 10 })
// 返回:{ id: 4, name: 'Node.js 实战', typeId: 10, ... }
// ========== 4. 查询 Tag(包含关联的 Type) ==========
await get(CourseService).getTags()
// 返回:[{ id: 1, name: 'Vue3 项目实战', typeId: 9, type: { id: 9, name: '推荐内容' } }, ...]
// ========== 5. 查询 Type(包含关联的 Tags) ==========
await get(CourseService).getTypeById(9)
// 返回:{ id: 9, name: '推荐内容', tags: [{ id: 1, name: 'Vue3 项目实战' }, ...] }
// ========== 6. 访问嵌套数据 ==========
const result = await get(CourseService).getTypeById(9)
result.tags[0]
// 返回:{ id: 1, name: 'Vue3 项目实战', typeId: 9 }
result.tags[0].name
// 返回:'Vue3 项目实战'八、REPL 模式最佳实践
8.1 使用技巧
code
REPL 模式使用技巧:
│
├── 1. 使用变量存储结果
│ ├── const result = await get(Service).method()
│ └── 方便后续操作和查看
│
├── 2. 使用 console.log 输出
│ ├── console.log(result)
│ └── 查看完整数据结构
│
├── 3. 分步调试
│ ├── 先测试简单方法
│ └── 逐步增加复杂度
│
├── 4. 保存常用命令
│ ├── 记录常用的查询命令
│ └── 提高调试效率
│
└── 5. 及时清理测试数据
├── 避免污染数据库
└── 使用 deleteMany 清理8.2 常见问题与解决方案
问题一:Service 未找到
javascript
// 错误:Service 未找到
Error: Cannot find module './course.service'
// 解决方案:确保 Service 正确导出
@Injectable()
export class CourseService {
// ...
}
// 在 Module 中注册
@Module({
providers: [CourseService],
exports: [CourseService], // 确保导出
})
export class CourseModule {}问题二:异步操作未使用 await
javascript
// 错误:未使用 await
const result = get(CourseService).createType({ name: 'test' })
// 返回:Promise { <pending> }
// 正确:使用 await
const result = await get(CourseService).createType({ name: 'test' })
// 返回:{ id: 9, name: 'test', ... }问题三:关联数据未返回
javascript
// 错误:未使用 include
await prisma.courseTypes.findUnique({ where: { id: 9 } })
// 返回:{ id: 9, name: '推荐内容' } // 没有 tags
// 正确:使用 include
await prisma.courseTypes.findUnique({
where: { id: 9 },
include: { tags: true },
})
// 返回:{ id: 9, name: '推荐内容', tags: [...] }九、REPL 模式 vs 传统调试方式对比
9.1 对比总结
| 维度 | REPL 模式 | Postman 调试 | 单元测试 |
|---|---|---|---|
| 启动速度 | 快速 | 需要启动服务器 | 需要配置测试环境 |
| 调试方式 | 直接调用方法 | HTTP 请求 | 编写测试用例 |
| 数据准备 | 直接插入 | 需要 API | 需要 fixture |
| 适用场景 | 快速验证、调试 | API 测试 | 自动化测试 |
| 学习成本 | 低 | 中 | 中 |
| 可重复性 | 手动执行 | 手动执行 | 自动执行 |
9.2 选择建议
code
调试方式选择建议:
│
├── REPL 模式
│ ├── 快速验证业务逻辑
│ ├── 调试复杂查询
│ ├── 插入测试数据
│ └── 学习和探索
│
├── Postman 调试
│ ├── API 接口测试
│ ├── 前后端联调
│ ├── 性能测试
│ └── 生产环境验证
│
└── 单元测试
├── 自动化测试
├── 回归测试
├── CI/CD 集成
└── 团队协作十、命令速查表
10.1 REPL 常用命令速查
| 命令 | 说明 | 示例 |
|---|---|---|
get(ServiceName) | 获取 Service 实例 | get(CourseService) |
await get(Service).method() | 调用异步方法 | await get(CourseService).createType({ name: 'test' }) |
help | 查看帮助 | help |
.exit | 退出 REPL | .exit |
Ctrl+D | 退出 REPL | Ctrl+D |
Ctrl+C | 取消当前输入 | Ctrl+C |
10.2 常用查询命令速查
| 操作 | 命令 |
|---|---|
| 创建 Type | await get(CourseService).createType({ name: '推荐内容' }) |
| 创建 Tag | await get(CourseService).createTag({ name: 'Vue3', typeId: 9 }) |
| 查询所有 Tags | await get(CourseService).getTags() |
| 查询 Type by ID | await get(CourseService).getTypeById(9) |
| 删除所有 Tags | await get(CourseService).deleteAllTags() |
| 删除所有 Types | await get(CourseService).deleteAllTypes() |
十一、学习要点总结
11.1 核心概念总结
code
NestJS REPL 模式核心要点:
│
├── REPL 模式特点
│ ├── 交互式编程环境
│ ├── 直接调用 Service 方法
│ └── 快速验证业务逻辑
│
├── 启动方式
│ └── pnpm start:repl
│
├── 基本用法
│ ├── await get(ServiceName) 获取实例
│ └── await get(ServiceName).method() 调用方法
│
├── 适用场景
│ ├── 快速插入测试数据
│ ├── 调试复杂查询
│ ├── 验证关联关系
│ └── 跳过 Controller 测试
│
└── Prisma include
├── 包含关联数据
├── 支持嵌套查询
└── 支持过滤排序11.2 学习路径规划
code
学习路径规划:
│
├── 第一阶段:理解概念(半天)
│ ├── 理解 REPL 模式的作用
│ ├── 掌握启动和基本命令
│ └── 理解适用场景
│
├── 第二阶段:实践操作(1-2 天)
│ ├── 使用 REPL 插入数据
│ ├── 使用 REPL 查询数据
│ └── 调试复杂查询逻辑
│
└── 第三阶段:深入应用(持续)
├── 结合实际项目使用
├── 优化调试流程
└── 提高开发效率11.3 重要提示
重要提示:REPL 模式是 NestJS 提供的强大调试工具,特别适合快速验证业务逻辑、插入测试数据、调试复杂查询。相比传统的 Postman 调试,REPL 模式可以直接调用 Service 方法,无需创建 Controller,大大提高了开发效率!记住使用
await处理异步操作,使用include查询关联数据!